package com.buli.blmall.admin.core.security.provider;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.buli.blmall.admin.common.domain.SysLoginUser;
import com.buli.blmall.admin.common.domain.SysUserDetail;
import com.buli.blmall.admin.consts.CacheKey;
import com.buli.blmall.admin.consts.Constants;
import com.buli.blmall.admin.core.listener.LoginEventData;
import com.buli.blmall.admin.core.security.domain.AuthenticationUser;
import com.buli.blmall.admin.enums.LoginType;
import com.buli.blmall.admin.exception.LoginException;
import com.buli.blmall.admin.exception.error.LoginErrorCode;
import com.buli.blmall.admin.system.entity.SysUser;
import com.buli.blmall.admin.system.service.SysPermissionService;
import com.buli.blmall.admin.utils.JwtUtil;
import com.buli.blmall.admin.utils.ServletUtil;
import com.buli.blmall.admin.core.cache.RedisClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @author xiang.gao
 * @date 2025/3/3 16:06
 */
@Slf4j
public abstract class BaseAuthProvider implements AuthenticationProvider {

    @Autowired
    protected RedisClient redisClient;

    @Autowired
    protected SysPermissionService sysPermissionService;

    /**
     * 创建授权用户
     * @param loginType
     * @param principal
     * @param sysUser
     * @return
     */
    protected AuthenticationUser createAuthentication(LoginType loginType, String principal, SysUser sysUser) {
        // 生成服务端token
        String serviceToken = UUID.randomUUID().toString();
        // 创建jwtToken
        String jwtToken = JwtUtil.createToken(serviceToken);
        SysLoginUser sysLoginUser = new SysLoginUser();
        sysLoginUser.setToken(jwtToken);
        sysLoginUser.setLoginType(loginType);
        sysLoginUser.setPrincipal(principal);
        sysLoginUser.setUserDetail(BeanUtil.toBean(sysUser, SysUserDetail.class));
        sysLoginUser.setPermission(sysPermissionService.gePermission(sysUser));
        // 缓存用户信息
        redisClient.setValue(CacheKey.SYSTEM_LOGIN_TOKEN.concat(serviceToken), sysLoginUser, CacheKey.SYSTEM_LOGIN_EXPIRE_TIME);
        return new AuthenticationUser(sysLoginUser);
    }


    /**
     * 登录验证
     *
     * @param loginType
     * @param principal
     * @param supplier
     */
    protected void checkLogin(LoginType loginType, String principal, Supplier<Boolean> supplier) {
        String errorKey = CacheKey.PWD_ERR_CNT_KEY + principal;
        int maxRetryCount = Constants.LOGIN_RETRY_MAX_COUNT;
        int lockTime = Constants.LOGIN_LOCK_TIME;
        // 获取用户登录错误次数
        Integer errorNumber = redisClient.getValue(errorKey);
        // 锁定时间内登录 则踢出
        if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
            recordLoginFailHistory(loginType, principal, Constants.LOGIN_FAIL, LoginErrorCode.RETRY_ERROR.getMesage());
            throw new LoginException(LoginErrorCode.RETRY_ERROR);
        }
        //登录失败
        if (supplier.get()) {
            // 是否第一次
            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
            LoginErrorCode errorCode;
            // 达到规定错误次数 则锁定登录
            if (errorNumber.equals(maxRetryCount)) {
                redisClient.setValue(errorKey, errorNumber, lockTime, TimeUnit.MINUTES);
                errorCode = LoginErrorCode.LOGIN_LOCKED;
            } else {
                // 未达到规定错误次数 则递增
                redisClient.setValue(errorKey, errorNumber);
                errorCode = LoginErrorCode.PASSWORD_ERROR;
            }
            recordLoginFailHistory(loginType, principal, Constants.LOGIN_FAIL, errorCode.getMesage());
            throw new LoginException(errorCode);
        }

        // 登录成功 清空错误次数
        if (errorNumber != null) {
            redisClient.deleteValue(errorKey);
        }
    }

    /**
     * 记录登录失败
     *
     * @param sysLogin
     * @param status
     * @param message
     */
    private void recordLoginFailHistory(LoginType sysLogin, String principal, String status, String message) {
        LoginEventData eventData = new LoginEventData();
        eventData.setLoginType(sysLogin.name());
        eventData.setPrincipal(principal);
        eventData.setStatus(status);
        eventData.setMessage(message);
        eventData.setLoginIp(ServletUtil.getClientIP());
        eventData.setDeviceInfo(ServletUtil.getHeader("User-Agent", Constants.UTF8));
        eventData.setLoginTime(DateUtil.date());
        // 异步记录登录日志
        SpringUtil.getApplicationContext().publishEvent(eventData);
    }

}
